package com.yycx.common.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
 * @author Stone
 * @version V1.0.0
 * @date 2021/3/18
 */
public class DateUtil {

    /**
     * 常用时间格式
     */
    public static final String Y_M_D_H_M_S = "yyyy-MM-dd HH:mm:ss";
    public static final String Y_M_D = "yyyy-MM-dd";
    public static final String YMD = "yyyyMMdd";
    public static final String Y_M_D_H_M_S_SSS = "yyyy-MM-dd HH:mm:ss.SSS";
    //public static final String DATE_FORMAT_UTC_TEMPLATE = "yyyy-MM-dd'T'HH:mm:ss'Z'";
    //public static final String DATE_FORMAT_UTC_TEMPLATE = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";

    /**
     * 常用时区
     */
    public static class TZ {
        public static final TimeZone UTC = TimeZone.getTimeZone("GMT+00:00");
        public static final TimeZone BEIJING = TimeZone.getTimeZone("GMT+08:00");
        public static final TimeZone TOKYO = TimeZone.getTimeZone("GMT+09:00");
        public static final TimeZone NIUYUE = TimeZone.getTimeZone("GMT-05:00");
        public static final TimeZone LOCAL = localTimeZone();
    }


    /**
     * 获取本地时区(指当前的时区，即Java程序所在JVM，JVM所在的操作系统的时区)
     *
     * @return 本地时区
     */
    public static TimeZone localTimeZone() {
        return TimeZone.getDefault();
    }

    /************************************ 时间类型互转，字串，Date，long BEGIN ************************************/
    /**
     * 时间戳转字串(本地时区)
     *
     * @param timestampWithMillis 时间戳，精确到毫秒，例如 System.currentTimeMillis() 的值
     * @param destDateFormat      要转成什么样的字串格式
     * @return 本地时区的日期时间的字串形式
     */
    public static String long2Str(long timestampWithMillis, String destDateFormat) {
        return long2StrTz(timestampWithMillis, destDateFormat, localTimeZone());
    }

    public static String long2StrUTC(long timestampWithMillis, String destDateFormat) {
        return long2StrTz(timestampWithMillis, destDateFormat, TZ.UTC);
    }

    /**
     * 时间戳转字串(指定时区)
     *
     * @param timestampWithMillis 时间戳，精确到毫秒，例如 System.currentTimeMillis() 的值
     * @param destDateFormat      要转成什么样的字串格式
     * @param destTimeZone        目的时区，即时间戳要转成什么时区。参考本类中定义的常用时区。
     * @return 指定时区的日期时间的字串形式
     */
    public static String long2StrTz(long timestampWithMillis, String destDateFormat, TimeZone destTimeZone) {
        SimpleDateFormat sdf = new SimpleDateFormat(destDateFormat);
        sdf.setTimeZone(destTimeZone);
        return sdf.format(timestampWithMillis);
    }

    /**
     * 时间戳转 Date
     *
     * <pre>
     * 注意：Date 是没有时区概念的，其内部保存了时间戳，toString() 时根据本地时区转成字串而已
     * </pre>
     *
     * @param timestampWithMillis 时间戳，到达毫秒级，如 System.currentTimeMillis()
     * @return 返回 java.util.Date
     */
    public static Date long2Date(long timestampWithMillis) {
        Date date = new Date(timestampWithMillis);
        return date;
    }


    /**
     * Date 转字串(本地时区)
     *
     * @param date           日期
     * @param destDateFormat 输出的字串格式
     * @return 本地时区的字串形式
     */
    public static String date2Str(Date date, String destDateFormat) {
        return date2StrTz(date, destDateFormat, localTimeZone());
    }

    public static String date2StrUTC(Date date, String destDateFormat) {
        return date2StrTz(date, destDateFormat, TZ.UTC);
    }

    /**
     * Date 转字串(指定时区)
     *
     * @param date           日期
     * @param destDateFormat 格式
     * @param destTimeZone   转成什么时区的字串
     * @return 指定时区的字串形式
     */
    public static String date2StrTz(Date date, String destDateFormat, TimeZone destTimeZone) {
        SimpleDateFormat sdf = new SimpleDateFormat(destDateFormat);
        sdf.setTimeZone(destTimeZone);
        return sdf.format(date);
    }

    /**
     * Date 转时间戳
     *
     * @param date 日期时间
     * @return 时间戳
     */
    public static long date2long(Date date) {
        return date.getTime();
    }


    /**
     * 字串(本地时区)转 Date
     *
     * <pre>
     * 注意：该方法把传入的日期字串当成是本地时区来进行转换的。
     *
     * 比如同样是 2017-04-02 15:33:08，在不同时区，
     * 返回的Date里的时间戳的值是不一样的！！即 date.getTime() 得到不一样的值
     *
     * 北京时区(东八区)
     *      Sun Apr 02 15:33:08 CST 2017
     *      1491118388000
     * 东京时区(东九区)
     *      Sun Apr 02 15:33:08 JST 2017
     *      1491114788000
     * </pre>
     *
     * @param localDateStr        待转换的字串
     * @param formatOfThisDateStr 字串的格式
     * @return 返回 "把该字串认为是本地时区的 java.util.Date"
     */
    public static Date str2Date(String localDateStr, String formatOfThisDateStr) {
        return str2DateTz(localDateStr, formatOfThisDateStr, localTimeZone());
    }

    public static Date str2DateUTC(String utcDateStr, String formatOfThisDateStr) {
        return str2DateTz(utcDateStr, formatOfThisDateStr, localTimeZone());
    }

    /**
     * 字串(本地时区)转 Date
     *
     * @param dateStr               待转换的字串
     * @param formatOfThisDateStr   字串的格式
     * @param timeZoneOfThisDateStr 字串的时区
     * @return 返回 "把该字串认为是本地时区的 java.util.Date"
     */
    public static Date str2DateTz(String dateStr, String formatOfThisDateStr, TimeZone timeZoneOfThisDateStr) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(formatOfThisDateStr);
            sdf.setTimeZone(timeZoneOfThisDateStr);
            return sdf.parse(dateStr);
        } catch (ParseException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * 字串(本地时区)转时间戳
     *
     * @param dateStr             待转换的字串
     * @param formatOfThisDateStr 字串的时区
     * @return 返回 "把该字串认为是本地时区的时间戳"
     */
    public static long str2long(String dateStr, String formatOfThisDateStr) {
        return str2longTz(dateStr, formatOfThisDateStr, localTimeZone());
    }

    public static long str2longUTC(String dateStr, String formatOfThisDateStr) {
        return str2longTz(dateStr, formatOfThisDateStr, TZ.UTC);
    }

    /**
     * 字串(指定时区)转时间戳
     *
     * @param dateStr               待转换的字串
     * @param formatOfThisDateStr   字串的时区
     * @param timeZoneOfThisDateStr
     * @return 返回 "把该字串认为是指定时区的时间戳"
     */
    public static long str2longTz(String dateStr, String formatOfThisDateStr, TimeZone timeZoneOfThisDateStr) {
        return date2long(str2DateTz(dateStr, formatOfThisDateStr, timeZoneOfThisDateStr));
    }


    /**
     * 传入时间字串，指定格式和时区，转换成指定的时区的时间字串
     *
     * @param srcDateString  源字串
     * @param srcDateFormat  源字串的格式
     * @param srcTimeZone    源字串的时区
     * @param destDateFormat 目的字串的格式
     * @param destTimeZone   目的字串的时区
     * @return 返回指定时区、指定格式的字串
     */
    public static String str2str(String srcDateString,
                                 String srcDateFormat,
                                 TimeZone srcTimeZone,
                                 String destDateFormat,
                                 TimeZone destTimeZone
    ) {
        SimpleDateFormat srcSdf = new SimpleDateFormat(srcDateFormat);
        srcSdf.setTimeZone(srcTimeZone);
        SimpleDateFormat destSdf = new SimpleDateFormat(destDateFormat);
        destSdf.setTimeZone(destTimeZone);
        Date sourceDate;
        try {
            sourceDate = srcSdf.parse(srcDateString);
            return destSdf.format(sourceDate);
        } catch (ParseException e) {
            throw new RuntimeException("Can not parse date", e);
        }
    }
    /************************************ 时间类型互转，字串，Date，long END ************************************/


    /************************************ 获取时间 BEGIN ************************************/
    /**
     * 获取本地日期时间
     * <p>
     * 这个 Date 其实是没有时区概念的，其里面存放的值是 System.currentTimeMillis() 的值
     * 但是 toString() 方法会根据系统时区来转成相应的字串
     * </p>
     *
     * @return 返回 java.util.Date
     */
    public static Date now() {
        return new Date();
    }

    /**
     * 获取 java.util.Date
     *
     * @return 获取日期部分(去掉了时分秒和毫秒)
     */
    public static Date nowDate() {
        return datePart(now());
    }

    /**
     * 获取时间戳(到达毫秒，13位数字)
     *
     * @return 实际能不能精度达到毫秒，要看操作系统，详细参考 System.currentTimeMillis() 内的 javadoc
     */
    private long currentTimeMillis() {
        return System.currentTimeMillis();
    }

    /**
     * 获取时间戳，精确到秒(也叫Unix时间戳，10位数字)
     *
     * @return 返回Unix时间戳(仅到达秒)
     */
    private long currentTimeSecs() {
        return System.currentTimeMillis() / 1000L;
    }

    /**
     * 获取日期部分
     *
     * @param date 日期时间
     * @return 返回只要日期部分的Date(注意 : 重新new了一个Date ， 入参得到Date不变化)
     */
    public static Date datePart(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        // 必须设置这个，否则毫秒数不同会导致compareDate方法在毫秒数上有差异
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }
    /************************************ 获取时间 END ************************************/


    /************************************ 时间顺序 BEGIN ************************************/
    /**
     * 获得日期是星期几
     *
     * @param date 日期时间
     * @return 返回1~7分别代表周一到周日
     */
    public static int getDayOfWeek(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        int index = cal.get(Calendar.DAY_OF_WEEK);
        switch (index) {
            case 1:
                return 7;
            case 2:
                return 1;
            case 3:
                return 2;
            case 4:
                return 3;
            case 5:
                return 4;
            case 6:
                return 5;
            default:
                return 6;
        }
    }


    /**
     * 获得日期是当月第几天,从1开始
     *
     * @param date 日期时间
     * @return 返回第几天, 从1开始
     */
    public static int getDayOfMonth(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        int index = cal.get(Calendar.DAY_OF_MONTH);
        return index;
    }

    /**
     * 获得某个日期所在月份一共有多少天
     *
     * @param date 日期时间
     * @return 日期所在月份有多少天
     */
    public static int getHowManyDaysInMonth(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        return cal.getActualMaximum(Calendar.DATE);
    }


    /**
     * 获得指定日期所在当月第一天(时间部分会保留)
     *
     * @param date 日期时间
     * @return 日期所在月份的开始那天
     */
    public static Date getStartDayOfMonth(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.set(Calendar.DATE, 1);
        return cal.getTime();
    }

    /**
     * 获得指定日期所在当月最后一天(时间部分会保留), 能正确识别月份不同天数
     *
     * @param date 日期时间
     * @return 日期所在月份最后一天
     */
    public static Date getLastDayOfMonth(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.set(Calendar.DATE, 1);
        cal.add(Calendar.MONTH, 1);
        cal.add(Calendar.DATE, -1);
        return cal.getTime();
    }
    /************************************ 时间顺序 END ************************************/


    /************************************ 时间比较 BEGIN ************************************/
    /**
     * 判断两个日期时间是否相等，连秒、毫秒都必须相等
     *
     * @param date1 日期时间1
     * @param date2 日期时间2
     * @return true则相等，反之不等
     */
    public static boolean isEqualDateTime(Date date1, Date date2) {
        return date1.getTime() == date2.getTime();
    }

    /**
     * 仅仅比较日期部分是否相等, 不比较时间部分<br>
     * 相等: 2015-08-19 14:28:47 和 2015-08-19 14:28:51、2015-08-18 和 2015-08-18<br>
     * 不等: 2015-08-18 和 2015-08-19、2015-08-19 14:32:51和2015-08-18 14:32:57
     *
     * @param date1 日期1
     * @param date2 日期2
     * @return true则相等，反之不等
     */
    public static boolean isEqualDate(Date date1, Date date2) {
        date1 = datePart(date1);
        date2 = datePart(date2);
        return isEqualDateTime(date1, date2);
    }

    /**
     * 比较日期时间先后顺序 <br>
     * 辅助理解: 1.所谓时间大于,未来的时间比现在大
     * 2.看着入参列出等式,如date1-date2,结果是1就表示大于0,即date1-date2>0亦即date1>date2
     *
     * @param date1
     * @param date2
     * @return date1>date2返回1,date1<date2返回-1,date1=date2返回0 <br>
     */
    public static int compareDateTime(Date date1, Date date2) {
        Calendar cal1 = Calendar.getInstance();
        cal1.setTime(date1);
        Calendar cal2 = Calendar.getInstance();
        cal2.setTime(date2);
        if (isEqualDateTime(date1, date2)) {
            return 0;
        }
        if (cal1.after(cal2)) {
            return 1;
        } else {
            return -1;
        }
    }

    /**
     * 比较日期的先后顺序<br>
     *
     * @param date1
     * @param date2
     * @return date1>date2返回1,date1<date2返回-1,date1=date2返回0 <br>
     */
    public static int compareDate(Date date1, Date date2) {
        date1 = datePart(date1);
        date2 = datePart(date2);
        return compareDateTime(date1, date2);
    }
    /************************************ 时间比较 END ************************************/


    /************************************ 时间加减 BEGIN ************************************/


    /**
     * 增加或减少年份数,注意入参本身是不改变的
     *
     * @param date   日期
     * @param amount 可以传负整数
     * @return 计算结果的新值, 入参本身不变!
     */
    public static Date addYears(Date date, int amount) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.YEAR, amount);
        return cal.getTime();
    }

    /**
     * 增加或减少月份数,注意入参本身是不改变的
     *
     * @param date   日期
     * @param amount 可以传负整数
     * @return 计算结果的新值, 入参本身不变!
     */
    public static Date addMonths(Date date, int amount) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.MONTH, amount);
        return cal.getTime();
    }

    /**
     * 增加或减少天数,注意入参本身是不改变的
     *
     * @param date   日期
     * @param amount 可以传负整数
     * @return 计算结果的新值, 入参本身不变!
     */
    public static Date addDays(Date date, int amount) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.DATE, amount);
        return cal.getTime();
    }


    /**
     * 增加或减少小时数,注意入参本身是不改变的
     *
     * @param date
     * @param amount 可以传负整数
     * @return 计算结果的新值, 入参本身不变!
     */
    public static Date addHours(Date date, int amount) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.HOUR_OF_DAY, amount);// 用HOUR和HOUR_OF_DAY结果一样
        return cal.getTime();
    }

    /**
     * 增加或减少分钟数,注意入参本身是不改变的
     *
     * @param date
     * @param amount 可以传负整数
     * @return 计算结果的新值, 入参本身不变!
     */
    public static Date addMinutes(Date date, int amount) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.MINUTE, amount);
        return cal.getTime();
    }

    /**
     * 增加或减少秒数,注意入参本身是不改变的
     *
     * @param date
     * @param amount 可以传负整数
     * @return 计算结果的新值, 入参本身不变!
     */
    public static Date addSeconds(Date date, int amount) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.SECOND, amount);
        return cal.getTime();
    }
    /************************************ 时间加减 END ************************************/


    /************************************ 时间间隔，其他 BEGIN ************************************/
    /**
     * 1. 计算两个日期之间有多少天(取了绝对值)<br>
     * 2. 只考虑日期,不考虑时间 <br>
     * 3. 包括开始日期,不包括结束 <br>
     * 4. 结果不会是负数 <br>
     * 5. 举例: 2015-08-19 和 2015-08-18 结果是1 2015-08-19和2015-08-19结果是0 <br>
     *
     * @param endDate   第一个时间
     * @param beginDate 第二个时间
     * @return 天数, 正数!
     */
    public static long getTwoDateInterval(Date endDate, Date beginDate) {
        endDate = datePart(endDate);
        beginDate = datePart(beginDate);
        return Math.abs((endDate.getTime() - beginDate.getTime()) / (24 * 60 * 60 * 1000L));
    }

    /**
     * 查询两个时间之间相差的天数,不足一天按一天计算<br>
     * 两个日期可以随便传按照任意顺序传入
     *
     * @param endDateTime   具体到时间,如2015-11-17 12:29:52
     * @param beginDateTime 具体到时间,如2015-11-17 12:29:52
     * @return
     */
    public static long getTwoDateTimeInterval(Date endDateTime, Date beginDateTime) {
        double d = ((double) (endDateTime.getTime() - beginDateTime.getTime())) / ((double) 24 * 60 * 60 * 1000L);
        d = Math.abs(d);
        Double day = Math.ceil(d);
        return Math.abs(day.longValue());
    }


    /**
     * 1. 计算两个日期之间有多少天<br>
     * 2. 只考虑日期,不考虑时间 <br>
     * 3. 既包括开始日期,也包括结束 <br>
     * 4. 举例: 2015-08-19 和 2015-08-18 结果是2 2015-08-19和2015-08-19结果是1 <br>
     *
     * @param endDate
     * @param beginDate
     * @return
     */
    public static long getTwoDateIntervalContainEnd(Date endDate, Date beginDate) {
        return getTwoDateInterval(endDate, beginDate) + 1;
    }


    /**
     * 获得传入的日期时间,距离当天 24:00:00 的分钟数
     * <pre>
     *     注意: 某天的 24:00:00 就是下一天的 00:00:00, 例如 2015-04-04 24:00:00,其实是 2015-04-05 00:00:00
     * </pre>
     *
     * @param date 日期时间
     * @return 返回分钟数, 不足一分钟算0分钟, 例如 2015-04-04 23:59:01,距离今天结束还有59秒,接口返回0. 计算的结果只和时间的部分有关,和日期部分无关
     */
    public static int howManyMinutesTillDayEnd(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.set(Calendar.HOUR_OF_DAY, 24);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        Date day24 = cal.getTime();
        return (int) ((day24.getTime() - date.getTime()) / (1000 * 60L));
    }


    /**
     * 获得传入的日期时间,距离当天 24:00:00 的秒数
     *
     * @param date 日期时间
     * @return 返回秒数. 计算的结果只和时间的部分有关,和日期部分无关
     */
    public static int howManySecondsTillDayEnd(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.set(Calendar.HOUR_OF_DAY, 24);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        Date day24 = cal.getTime();
        return (int) ((day24.getTime() - date.getTime()) / 1000L);
    }

    public static String currentDateTime() {
        LocalDateTime localDateTime = LocalDateTime.now();
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
        String now = dateTimeFormatter.format(localDateTime);
        return now;
    }

    /************************************ 时间间隔，其他 END ************************************/

    public static  void main(String[] args){
        Date localDate=DateUtils.getNowDateTime();
        System.out.println("北京时间:"+DateUtils.formatDate(localDate,"YYYY-MM-dd HH:mm:dd")+"美国当前时间"+date2StrTz(localDate,"YYYY-MM-dd HH:mm:dd",TZ.NIUYUE));

    }
}
